﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using BluePortal.Framework;

namespace BluePortal.Medias.Core
{
    public class BCMedia : BCMedia<Media> 
    {
        
    }

    public class BCMedia<T> : AbstractBusinessComponent where T : Media
    {       
        #region IQueryable Methods

        private IQueryable<T> Filtered(IQueryable<T> baseQuery) 
        {
            if (typeof(T) != typeof(Media))
                return baseQuery.OfType<T>();
            return baseQuery.Cast<T>();           
        }

        private IQueryable<T> Filtered(IQueryable<Media> baseQuery)
        {
            if (typeof(T) != typeof(Media))
                return baseQuery.OfType<T>();
            return baseQuery.Cast<T>();
        }

        public IQueryable<T> QueryFindByChannelWithViewCount(string loginName, Guid chid)
        {
            return QueryFindByChannel(chid);
            //if (loginName != string.Empty)
            //{
            //    var query = from v in QueryFindAllFavoriesWithPlaylist(loginName)
            //                from c in v.VideoChannels
            //                where c.CHID == chid &&
            //                    v.Status == (int)BluePortal.Core.Interfaces.VideoStatus.Online
            //                select v;

            //    foreach (Media v in query)
            //    {
            //        var q = from fav in Context.Favorites where fav.LoginName == loginName && fav.ID == v.ID select fav;
            //        foreach (Favorite f in q)
            //            v.Favorites.Add(f);
            //    }
            //    return query;
            //}
            //else
            //{
            //    var query = from v in Context.Medias
            //                from c in v.VideoChannels
            //                where c.CHID == chid &&
            //                    v.Status == (int)BluePortal.Core.Interfaces.VideoStatus.Online
            //                select v;
            //    return query;
            //}
        }

        //@todo remplace QueryFindAllFavoriesWithPlaylist -> pourquoi pas les videos owned ???       
        public IQueryable<T> QueryFindAllForUser(string loginName)
        {
            IQueryable<Media> query = from v in Context.Medias
                        //from p in Context.MediaPlaylists
                        where (v.Owner == loginName) //|| (p.Playlist.LoginName == loginName)
                        select v;
            return Filtered(query);
        }

        public IQueryable<T> QueryFindAllForUserWithViewCount(string loginName)
        {
            return QueryFindAllForUser(loginName);
            //if (loginName != string.Empty)
            //{
            //    var query = from v in QueryFindAllFavoriesWithPlaylist(loginName)
            //                where v.Status == (int)BluePortal.Core.Interfaces.VideoStatus.Online
            //                select v;

            //    foreach (Media v in query)
            //    {
            //        var q = from fav in Context.Favorites where fav.LoginName == loginName && fav.ID == v.ID select fav;
            //        foreach (Favorite f in q)
            //            v.Favorites.Add(f);
            //    }
            //    return query;
            //}
            //else
            //{
            //    var query = from v in Context.Medias
            //                where v.Status == (int)BluePortal.Core.Interfaces.VideoStatus.Online
            //                select v;
            //    return query;
            //}
        }

        public IQueryable<T> QueryFindTopRating(string loginName, int count)
        {
            //var query = from v in QueryFindAllWithFavoriesWithPlaylistWithViewCount(loginName)
            var query = from v in QueryFindAll(loginName)
                        orderby v.Rating descending
                        select v;                
            return Filtered(query).Take(count);
        }

        public IQueryable<T> QueryFindLastUpload(string loginName, int count)
        {
            //var query = from v in QueryFindAllWithFavoriesWithPlaylistWithViewCount(loginName)       
            var query = from v in QueryFindAll(loginName)
                        orderby v.PublishDate descending
                        select v;
            return Filtered(query).Take(count);
        }

        public IQueryable<T> QueryFindTopView(string loginName, int count)
        {
            //var query = from v in QueryFindAllWithFavoriesWithPlaylistWithViewCount(loginName)
            var query = from v in QueryFindAll(loginName)
                        orderby v.Views.Count descending
                        select v;
            return Filtered(query).Take(count);
        }

        public IQueryable<T> QueryFindByCategory(Guid categoryID)
        {
            if ((categoryID == null) || (categoryID == Guid.Empty))
                return null;
            IQueryable<Media> query = from v in Context.Medias
                                  from vc in v.MediaCategories
                                  where vc.CID == categoryID && v.Status == (int)MediaStatus.Online
                                  select v;
            return Filtered(query);
        }

        public IQueryable<T> QueryFindByChannel(Guid channelID)
        {
            if ((channelID == null) || (channelID == Guid.Empty))
                return null;

            IQueryable<Media> query = from v in Context.Medias
                                      from vch in v.MediaChannels
                                      where vch.CHID == channelID && v.Status == (int)MediaStatus.Online
                                      select v;
            return Filtered(query);
        }

        public IQueryable<T> QueryFindByParentCategory(Guid parentCategoryID)
        {
            if ((parentCategoryID == null) || (parentCategoryID == Guid.Empty))
                return null;

            IQueryable<Media> query = from v in Context.Medias
                                    from vc in v.MediaCategories
                                    where vc.Category.ParentCategory == parentCategoryID
                                    select v;
            return Filtered(query);
        }

        public IQueryable<T> QueryFindByPlayList(Guid plid)
        {
            var query = from v in Context.Medias
                        from vpl in v.MediaPlaylists
                        where vpl.PLID == plid
                        select v;
            return Filtered(query);
        }

        public IQueryable<T> QueryFindByTag(string tag)
        {
            var query = from v in Context.Medias
                        from t in v.MediaTags
                        where t.Tag.TagName == tag
                        select v;
            return Filtered(query);
        }

        public IQueryable<T> QueryFindAllByUserWithViewCount(string loginName)
        {
            return QueryFindAllForUser(loginName);
            //DataLoadOptions ds = new DataLoadOptions();
            //ds.LoadWith<T>(v => v.Status);
            //Context.LoadOptions = ds;

            //var query = from v in QueryFindAllFavoriesWithPlaylist(loginName)
            //            where v.Owner == loginName
            //            orderby v.Status
            //            select v;

            //foreach (Media v in query)
            //{
            //    var q = from fav in Context.Favorites where fav.LoginName == loginName && fav.ID == v.ID select fav;
            //    foreach (Favorite f in q)
            //        v.Favorites.Add(f);
            //}
            //return query;
        }
        
        public IQueryable<T> QueryFindTopContributor(string loginName, int count)
        {
            //var query = (from v in QueryFindAllWithFavoriesWithPlaylistWithViewCount(loginName)
            var query = (from v in QueryFindAllForUser(loginName)
                         orderby v.PublishDate descending
                         group v by v.Owner into g
                         orderby g.Count() descending
                         select g.First());
            return Filtered(query).Take(5);
        }

        public IQueryable<T> QueryFindAll()
        {
            var query = from v in Context.Medias
                        select v;
            return Filtered(query);
        }

        /// <summary>
        /// Find all video visible for a user 
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public IQueryable<T> QueryFindAll(string username)
        {
            var query = from v in Context.Medias
                        where v.Status == (int) MediaStatus.Online
                        select v;
            return Filtered(query);
        }
        #endregion 

        #region FindResult 
        public FindResult<T> FindAll(FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindAll()), findParameters);
        }

        public FindResult<T> FindByPlayList(Guid plid, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindByPlayList(plid)), findParameters);
        }

        public FindResult<T> FindByCategory(Guid cid, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindByCategory(cid)), findParameters);
        }

        public FindResult<T> FindByChannel(Guid chid, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindByChannel(chid)), findParameters);
        }

        public FindResult<T> FindByParentCategory(Guid pcid, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindByParentCategory(pcid)), findParameters);
        }

        public FindResult<T> FindByTag(string tag, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindByTag(tag)), findParameters);
        }



        //public FindResult<R> FindByTag<R>(string tag, FindParameters findParameters, Func<T, R> selector)
        //{
        //    var q = Filtered(QueryFindByTag(tag)).Select<T, R>(selector);
        //    return ToFindResult<R>(q, findParameters);
        //}



        public IEnumerable<T> FindTopRating(string loginName, int count)
        {
            return Filtered(QueryFindTopRating(loginName, count)).AsEnumerable();
        }

        public IEnumerable<T> FindLastUpload(string loginName, int count)
        {
            return Filtered(QueryFindLastUpload(loginName, count)).AsEnumerable();
        }

        public IEnumerable<T> FindTopContributor(string loginName, int count)
        {
            return Filtered(QueryFindTopContributor(loginName, count)).AsEnumerable();
        }

        public IEnumerable<T> FindTopView(string loginName, int count)
        {
            return Filtered(QueryFindTopView(loginName, count)).AsEnumerable();
        }

        public FindResult<T> FindAllByUserWithViewCount(string loginName, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindAllByUserWithViewCount(loginName)), findParameters);
        }

        public FindResult<T> FindByChannelWithViewCount(string loginName, Guid chid, FindParameters findParameters)
        {
            return ToFindResult<T>(Filtered(QueryFindByChannelWithViewCount(loginName, chid)), findParameters);
        }

        #endregion 

        public void MakeOffLine (T media)
        {
            media.Status = (int) MediaStatus.OffLine;
            Context.SubmitChanges();
        }

        public Media FindByID(Guid id)
        {
            return Context.Medias.SingleOrDefault(v => v.ID == id);
        }

        public void AddRating(Media media, string loginName, int ratingValue)
        {
            AssertNotNull(media, "media");
            AssertNotNull(loginName, "loginName");
            AddRating(media.ID, loginName, ratingValue);
        }

        public void AddRating(Guid ID, string loginName, int ratingValue)
        {
            using (TransactionScope tr = new TransactionScope())
            {
                if (ratingValue < 1 || ratingValue > 5)
                    throw new ArgumentOutOfRangeException("ratingValue", "must be between 1 to 5");
                var media = Context.Medias.Single(v => v.ID == ID);

                var query = from v in Context.Ratings
                            where v.ID == ID
                            select v;

                //Brack Linq To SQL link for update query and average query
                var enumQuery = query.AsEnumerable();

                //Add or Update
                Rating VideoRating = enumQuery.SingleOrDefault(vr => vr.ID == ID && vr.LoginName == loginName);
                if (VideoRating == null)
                    Context.Ratings.InsertOnSubmit(new Rating() { ID = ID, LoginName = loginName, Rate = ratingValue });
                else
                    VideoRating.Rate = ratingValue;

                //Calculat Average
                double? avg;
                if (enumQuery.Count() != 0)
                    avg = enumQuery.Average(v => v.Rate);
                else
                    avg = ratingValue;
                media.Rating = (int?)avg;
                Context.SubmitChanges();

                tr.Complete();
            }
        }

        public void AddView(Media m, string loginName)
        {
            AddView(m.ID, loginName);
        }

        public void AddView(Guid ID, string loginName)
        {
            throw new Exception("Not implemented");
            //Context.AddMediaViewCount(ID, loginName);
        }

        public int FindUserRating(Guid ID, string loginName)
        {
            var query = Context.Ratings.SingleOrDefault(vr => vr.ID == ID && vr.LoginName == loginName);
            if (query != null)
                return query.Rate != null ? query.Rate.Value : 0;
            else
                return 0;
        }

        public void Delete(Guid id)
        {
            var query = Context.Medias.SingleOrDefault(v => v.ID == id);
            if (query != null)
            {
                Context.Medias.DeleteOnSubmit(query);
                Context.SubmitChanges();
            }
        }
    }
}
